Signals of Physical Processes |
Some time signals (or time sequence) exhibit changes in amplitude, frequency and phase with time. When computing the Fourier Transform of these signals (sequence), the Fourier Transform is not appropriate. One easy solution is to divide the original signal (sequence) in blocks and compute the Fourier Transform for each block as shown in the figure below. |
Time-Dependent Fourier Transform |
The Time-Dependent Fourier Transform (TDFT) is also known as the short-time Fourier Transform (STFT). The TDFT can be defined for continuous time signals and for discrete time signals. The equation below shows the definition of the TDFT. As it can be seen the input sequence x[n] is transform in a sequence of two discrete variables: n and k. |
The Implementation of the Time-Dependent Fourier Transform |
The previous definition of the TDFT can be rewritten so that it can be implemented as a filter bank as shown in the figure below. As it can be seen, an input sequence x[n] is transform in N sequences. |
Kaiser Window in a TDFT |
When the Kaiser window is used to compute the TDFT, it is possible to adjust the shape of the window using the parameter beta as shown below. |
Gabor Transform |
When a Gaussian window is used in the Time-Dependent Fourier Transform, the transform is called the Gabor Transform. The equation of the Gaussian window is shown below. |
Problem 1 |
Create a Dialog Application called TimeDependentFTView using Wintempla to show the Time-Dependent Fourier Transform. Add a Wintempla Custom Control called Display using the menu Tools > Add Wintempla Item... > Custom Control. Then, edit the GUI as shown in the figure below. |
TimeDependentFTView.h |
#pragma once //______________________________________ TimeDependentFTView.h #include "resource.h" #include "Display.h" #define RESOLUTION 1000 #define MAX_BETA 10.0 #define MAX_NUM_FREQ POINT_COUNT #define MAX_WINDOW_WITH (MAX_NUM_FREQ-1) class TimeDependentFTView: public Win::Dialog { public: TimeDependentFTView() { positionA = 0; positionB = 0; positionC = RESOLUTION/2; positionBeta = RESOLUTION/2; positionWindowWidth = RESOLUTION/2; positionNumFreq = RESOLUTION; y.resize(POINT_COUNT); } ~TimeDependentFTView() { } int positionA; int positionB; int positionC; int positionBeta; int positionWindowWidth; int positionNumFreq; //Math::GaborTransform gaborTransform; Math::TimeDependentFT transform; valarray<double> y; void Run(); void CreateFilterBank(); protected: //______ Wintempla GUI manager section begin: DO NOT EDIT AFTER THIS LINE ... }; |
TimeDependentFTView.cpp |
#include "stdafx.h" //________________________________________ TimeDependentFTView.cpp #include "TimeDependentFTView.h" int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE , LPTSTR cmdLine, int cmdShow){ TimeDependentFTView app; return app.BeginDialog(IDI_TIMEDEPENDENTFTVIEW, hInstance); } void TimeDependentFTView::Window_Open(Win::Event& e) { //________________________________________________________ sldA sldA.SetRange(0, RESOLUTION); sldA.Position = 0; tbxA.DoubleValue = 0.0; //________________________________________________________ sldB sldB.SetRange(0, RESOLUTION); sldB.Position = 0; tbxB.DoubleValue = 0.0; //________________________________________________________ sldC sldC.SetRange(0, RESOLUTION); sldC.Position = RESOLUTION/2; tbxC.DoubleValue = MAX_VALUE/2.0; //________________________________________________________ sldNumbFrequencies sldNumbFrequencies.SetRange(1, RESOLUTION); sldNumbFrequencies.Position = RESOLUTION; tbxNumFrequencies.IntValue = (int)((double)MAX_NUM_FREQ*sldNumbFrequencies.Position/RESOLUTION); //________________________________________________________ sldWindowWidth sldWindowWidth.SetRange(1, RESOLUTION); sldWindowWidth.Position = RESOLUTION/2; tbxWindowWidth.IntValue = (int)(0.5*MAX_WINDOW_WITH*sldWindowWidth.Position/RESOLUTION); //________________________________________________________ sldBeta sldBeta.SetRange(0, RESOLUTION); sldBeta.Position = RESOLUTION/2; tbxBeta.DoubleValue = MAX_BETA*sldBeta.Position/RESOLUTION; //________________________________________________________ xyInput xyInput.CaptionX = L"Time"; xyInput.CaptionY = L"y"; xyInput.MinX= 0.0; xyInput.MaxX= 1.0; xyInput.MinY= -1.2; xyInput.MaxY= 1.2; xyInput.DivisionCountY = 12; xyInput.Graphs.Add(POINT_COUNT); xyInput.SubgridColor = RGB(0, 20, 60); xyInput.Graphs[0].Color = RGB(0, 255, 0); const double delta_t = 1.0/POINT_COUNT; for(int i=0; i<POINT_COUNT; i++) { xyInput.Graphs[0][i].x = i*delta_t; xyInput.Graphs[0][i].y = 0.0; } xyInput.RefreshAll(); radioSin.Checked = true; CreateFilterBank(); Run(); } void TimeDependentFTView::CreateFilterBank() { const double beta = tbxBeta.DoubleValue; const int windowWidth = tbxWindowWidth.IntValue; const int numFreq = tbxNumFrequencies.IntValue; if (transform.Create(windowWidth, numFreq, beta) == false) { this->tbxWindowWidth.ShowBalloonTip(L"The window width must be less than the number of frequencies", L"TimeDependentFTView", TTI_ERROR); } else { this->tbxWindowWidth.HideBalloonTip(); } } void TimeDependentFTView::sldA_Hscroll(Win::Event& e) { const int newPosition = sldA.Position; if (newPosition == positionA) return; positionA = newPosition; // tbxA.DoubleValue = (MAX_VALUE*sldA.Position/RESOLUTION); Run(); } void TimeDependentFTView::sldB_Hscroll(Win::Event& e) { const int newPosition = sldB.Position; if (newPosition == positionB) return; positionB = newPosition; // tbxB.DoubleValue = (MAX_VALUE*sldB.Position/RESOLUTION); Run(); } void TimeDependentFTView::sldC_Hscroll(Win::Event& e) { const int newPosition = sldC.Position; if (newPosition == positionC) return; positionB = newPosition; // tbxC.DoubleValue = (MAX_VALUE*sldC.Position/RESOLUTION); Run(); } void TimeDependentFTView::Run() { Win::HourGlassCursor hgc(true); const double a = tbxA.DoubleValue; const double b = tbxB.DoubleValue; const double c = tbxC.DoubleValue; double t; double f; const double delta_t = 1.0/POINT_COUNT; const double twopi = 2.0*M_PI; int i; if (radioSin.Checked == true) { for(i = 0; i < POINT_COUNT; i++) { t = i*delta_t; f = a*t*t+b*t+c; y[i] = sin(twopi*f*t); xyInput.Graphs[0][i].y = y[i]; } } else if (radioTriangular.Checked == true) { for(i = 0; i < POINT_COUNT; i++) { t = i*delta_t; f = a*t*t+b*t+c; y[i] = Math::Dsp::Triangular(twopi*f*t, twopi); xyInput.Graphs[0][i].y = y[i]; } } else if (radioRectangular.Checked == true) { for(i = 0; i < POINT_COUNT; i++) { t = i*delta_t; f = a*t*t+b*t+c; y[i] = Math::Dsp::Rectangular(twopi*f*t, twopi); xyInput.Graphs[0][i].y = y[i]; } } xyInput.RefreshAll(); transform.Transform(y, customControlDisplay.output); //gaborTransform.Transform(y, customControlDisplay.output); customControlDisplay.UpdateImage(); } void TimeDependentFTView::radioSin_Click(Win::Event& e) { Run(); } void TimeDependentFTView::radioTriangular_Click(Win::Event& e) { Run(); } void TimeDependentFTView::radioRectangular_Click(Win::Event& e) { Run(); } void TimeDependentFTView::sldNumbFrequencies_Hscroll(Win::Event& e) { const int newPosition = sldNumbFrequencies.Position; if (newPosition == positionNumFreq) return; positionNumFreq = newPosition; // tbxNumFrequencies.IntValue = (int)((double)MAX_NUM_FREQ*newPosition/RESOLUTION); CreateFilterBank(); Run(); } void TimeDependentFTView::sldWindowWidth_Hscroll(Win::Event& e) { const int newPosition = sldWindowWidth.Position; if (newPosition == positionWindowWidth) return; positionWindowWidth = newPosition; // tbxWindowWidth.IntValue = (int)(0.5*MAX_WINDOW_WITH*positionWindowWidth/RESOLUTION); CreateFilterBank(); Run(); } void TimeDependentFTView::sldBeta_Hscroll(Win::Event& e) { const int newPosition = sldBeta.Position; if (newPosition == positionBeta) return; positionBeta = newPosition; // tbxBeta.DoubleValue = MAX_BETA*positionBeta/RESOLUTION; CreateFilterBank(); Run(); } |
Display.h |
#pragma once #define MAX_VALUE 100.0 #define POINT_COUNT 512 #define BITMAP_WIDTH 512 #define BITMAP_HEIGHT 512 class Display: public Win::Window { public: Display(); ~Display(); void ComputeBits(); bool IsEvent(Win::Event& e, int notification); MATRIXC output; void UpdateImage(); private: DWORD* bits; CG::DDBitmap bitmap; const wchar_t * GetClassName(void){return L"DISPLAY";} static bool isRegistered; protected: //______ Wintempla GUI manager section begin: DO NOT EDIT AFTER THIS LINE void Window_Open(Win::Event& e); void Window_Paint(Win::Event& e); void Window_Size(Win::Event& e); }; |
Display.cpp |
// Display.cpp #include "stdafx.h" #include "Display.h" bool Display::isRegistered= false; Display::Display() { if (!this->isRegistered) { this->RegisterClassEx( LoadCursor(NULL, IDC_ARROW), // Cursor: IDC_IBEAM, IDC_WAIT, IDC_CROSS, ... (HBRUSH)(COLOR_BTNFACE+1)); //Background: (HBRUSH)(COLOR_WINDOW+1)), ::GetStockObject(BLACK_BRUSH)... this->isRegistered = true; } bits = NULL; } Display::~Display() { if (bits != NULL) delete [] bits; } void Display::UpdateImage() { int row, col; double value; const int data_rows = output.size(); const int data_cols = (data_rows == 0) ? 0 : output[0].size(); int maxrow, maxcol; const complex<double> maximum = Math::Statistics::GetMagnitudeMax(output, maxrow, maxcol); if (maxrow == -1) return; double maxMagnitude = abs(maximum); if (maxMagnitude== 0) maxMagnitude = 0.000001; COLORREF color; // for(row = 0; row < BITMAP_HEIGHT; row++) { for(col = 0; col < BITMAP_WIDTH; col++) { if (row < data_rows && col < data_cols) { value = abs(output[row][col]); value /= maxMagnitude; color = Sys::Convert::DoubleToColorRef(value, false, 5); } else { color = RGB(255, 255, 255); } bits[col + BITMAP_WIDTH*(BITMAP_HEIGHT-row-1)] = color; } } bitmap.CreateCompatibleFromBits(hWnd, BITMAP_WIDTH, BITMAP_HEIGHT, bits); ::InvalidateRect(hWnd, NULL, FALSE); } void Display::Window_Open(Win::Event& e) { bits = new DWORD[BITMAP_WIDTH*BITMAP_HEIGHT]; } void Display::Window_Paint(Win::Event& e) { CG::Gdi gdi(e.hWnd, true, false); gdi.DrawCompatibleBitmap(bitmap, 0, 0); } void Display::Window_Size(Win::Event& e) { } bool Display::IsEvent(Win::Event& e, int notification) { if (e.uMsg!=WM_COMMAND) return false; const int id = LOWORD(e.wParam); const int notificationd = HIWORD(e.wParam); if (id != this->id) return false; if (notificationd!=notification) return false; return true; } |
Problem 2 |
Create a Dialog Application called GaborView using Wintempla to show the Gabor Transform. The file Display.h and Display.cpp are the same as those of problem 1. After creating the application edit the GUI as shown, copy the files Display.h and Display.cpp from the TimeDependentFTView project to the folder of the GaborView project. Using the menu Project > Add existing item... , add the files Display.h and Display.cpp to the GaborView project. |
GaborView.h |
#pragma once //______________________________________ GaborView.h #include "resource.h" #include "Display.h" #define RESOLUTION 1000 class GaborView: public Win::Dialog { public: GaborView() { positionA = 0; positionB = 0; positionC = RESOLUTION/2; positionNumFreq = POINT_COUNT; y.resize(POINT_COUNT); } ~GaborView() { } int positionA; int positionB; int positionC; int positionNumFreq; Math::GaborTransform transform; valarray<double> y; void Run(); void CreateFilterBank(); protected: ... }; |
GaborView.cpp |
#include "stdafx.h" //________________________________________ GaborView.cpp #include "GaborView.h" int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE , LPTSTR cmdLine, int cmdShow){ GaborView app; return app.BeginDialog(IDI_GABORVIEW, hInstance); } void GaborView::Window_Open(Win::Event& e) { //________________________________________________________ sldA sldA.SetRange(0, RESOLUTION); sldA.Position = 0; tbxA.DoubleValue = 0.0; //________________________________________________________ sldB sldB.SetRange(0, RESOLUTION); sldB.Position = 0; tbxB.DoubleValue = 0.0; //________________________________________________________ sldC sldC.SetRange(0, RESOLUTION); sldC.Position = RESOLUTION/2; tbxC.DoubleValue = MAX_VALUE/2.0; //________________________________________________________ sldNumbFreq sldNumbFreq.SetRange(1, POINT_COUNT); sldNumbFreq.Position = POINT_COUNT/2; tbxNumbFreq.IntValue = POINT_COUNT/2; //________________________________________________________ xyInput xyInput.CaptionX = L"Time"; xyInput.CaptionY = L"y"; xyInput.MinX= 0.0; xyInput.MaxX= 1.0; xyInput.MinY= -1.2; xyInput.MaxY= 1.2; xyInput.DivisionCountY = 12; xyInput.Graphs.Add(POINT_COUNT); xyInput.SubgridColor = RGB(0, 20, 60); xyInput.Graphs[0].Color = RGB(0, 255, 0); const double delta_t = 1.0/POINT_COUNT; for(int i=0; i<POINT_COUNT; i++) { xyInput.Graphs[0][i].x = i*delta_t; xyInput.Graphs[0][i].y = 0.0; } xyInput.RefreshAll(); // radioSin.Checked = true; CreateFilterBank(); Run(); } void GaborView::CreateFilterBank() { const int numFreq = tbxNumbFreq.IntValue; if (transform.Create(numFreq) == false) { this->Text = L"The input parameters are invalid"; } else { this->Text = L"GaborView"; } } void GaborView::sldA_Hscroll(Win::Event& e) { const int newPosition = sldA.Position; if (newPosition == positionA) return; positionA = newPosition; // tbxA.DoubleValue = (MAX_VALUE*sldA.Position/RESOLUTION); Run(); } void GaborView::sldB_Hscroll(Win::Event& e) { const int newPosition = sldB.Position; if (newPosition == positionB) return; positionB = newPosition; // tbxB.DoubleValue = (MAX_VALUE*sldB.Position/RESOLUTION); Run(); } void GaborView::sldC_Hscroll(Win::Event& e) { const int newPosition = sldC.Position; if (newPosition == positionC) return; positionB = newPosition; // tbxC.DoubleValue = (MAX_VALUE*sldC.Position/RESOLUTION); Run(); } void GaborView::sldNumbFreq_Hscroll(Win::Event& e) { const int newPosition = sldNumbFreq.Position; if (newPosition == positionNumFreq) return; positionNumFreq = newPosition; // tbxNumbFreq.IntValue = positionNumFreq; CreateFilterBank(); Run(); } void GaborView::Run() { Win::HourGlassCursor hgc(true); const double a = tbxA.DoubleValue; const double b = tbxB.DoubleValue; const double c = tbxC.DoubleValue; double t; double f; const double delta_t = 1.0/POINT_COUNT; const double twopi = 2.0*M_PI; int i; if (radioSin.Checked == true) { for(i = 0; i < POINT_COUNT; i++) { t = i*delta_t; f = a*t*t+b*t+c; y[i] = sin(twopi*f*t); xyInput.Graphs[0][i].y = y[i]; } } else if (radioTriangular.Checked == true) { for(i = 0; i < POINT_COUNT; i++) { t = i*delta_t; f = a*t*t+b*t+c; y[i] = Math::Dsp::Triangular(twopi*f*t, twopi); xyInput.Graphs[0][i].y = y[i]; } } else if (radioRectangular.Checked == true) { for(i = 0; i < POINT_COUNT; i++) { t = i*delta_t; f = a*t*t+b*t+c; y[i] = Math::Dsp::Rectangular(twopi*f*t, twopi); xyInput.Graphs[0][i].y = y[i]; } } xyInput.RefreshAll(); transform.Transform(y, customControlDisplay.output); customControlDisplay.UpdateImage(); } void GaborView::radioSin_Click(Win::Event& e) { Run(); } void GaborView::radioTriangular_Click(Win::Event& e) { Run(); } void GaborView::radioRectangular_Click(Win::Event& e) { Run(); } |